;=================================================
; custom macros sample
;
; :DOC: defmacro skeleton
; (defmacro name (parameter*)
;  "Optional documentation string."
;  body-form*)


;=================================================
; test own when macro
(defun test-when-macro ()

 ; defining custom `when' macro
 (defmacro when-test (condition &rest body)
  `(if ,condition (progn ,@body)))

 ; test it
 (setq x 1)
 (when-test (< x 10) (format t "x = ~a~%" x) (1+ x))
 ) ; test-when-macro


;=================================================
; test do-primes macro
;
(defun test-do-primes-macro ()
 ; utility function to check whether the given number is prime or not
 (defun primep (number)
  (when (> number 1)
   (loop for fac from 2 to (isqrt number) never (zerop (mod number fac))))
  )

 ; utility function to get prime number higher than the given one
 (defun next-prime (number)
  (loop for n from number when (primep n) return n)
  )

 ; the idea of macro-body (primes from 0 to 19)
 ;(do ((p (next-prime 0) (next-prime (1+ p))))
 ;    ((> p 19))
 ;    (format t "~d " p)
 ;    )

 ; 1st option
 (defmacro do-primes-1 (var-and-range &rest body)
  (let ((var (first var-and-range))
        (start (second var-and-range))
        (end (third var-and-range)))
   `(do ((,var (next-prime ,start) (next-prime (1+ ,var))))
        ((> ,var ,end))
        ,@body))
  ) ; defmacro

 ; 2nd option: destructuring and using &body instead of &rest
 ; :DOC:
 ; typically &body parameters are used to hold a list of forms
 ; that make up the body of the macro
 (defmacro do-primes-2 ((var start end) &body body)
  `(do ((,var (next-prime ,start) (next-prime (1+ ,var))))
       ((> ,var ,end))
       ,@body)
  ) ; defmacro

 ; 3rd option: how it can be without using back quotes
 (defmacro do-primes-3 ((var start end) &body body)
  (append '(do)
          (list  (list (list var
                             (list 'next-prime start)
                             (list 'next-prime (list '1+ var)))))
          (list (list (list '> var end)))
          body)
  ) ; defmacro

 ; how to see to what it looks like
 (format t "do-primes-2 expanded: ~a~%"
 (macroexpand-1 '(do-primes-2 (p 0 19) (format t "~d " p)))
 ) ; format

 ; showing leak in abstraction: end-condition is not calculated once
 (defun get-how-much ()
  (defvar *counter* 50)
  (setf *counter* (- *counter* 10))
  )
 ; here: get-how-much called each time when the final-condition checked 
 (do-primes-2 (p 0 (get-how-much))
  (format t "~d " p))

 ; the solution of the problem is to introduce end-condition variable;
 ; it is tricky - because it should never be used neither in
 ; the macro body, nor in the former code.
 ; there is a function gensym which should be used in such cases
 (defmacro do-primes-4 ((var start end) &body body)
  (let ((ending-value-name (gensym)))
   `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
         (,ending-value-name ,end))
        ((> ,var ,ending-value-name))
        ,@body))
  ) ; defmacro

 ; one another solution: use once-only:
 (defmacro do-primes ((var start end) &body body)
  (once-only (start end)
   `(do ((,var (next-prime ,start) (next-prime (1+ ,var))))
        ((> ,var ,end))
        ,@body)
   )
  ) ; defmacro
 ; here: once-only really uses gensym in its work:
 ;(defmacro once-only ((&rest names) &body body)
 ; (let ((gensyms (loop for n in names collect (gensym))))
 ;   `(let (,@(loop for g in gensyms collect `(,g (gensym))))
 ;     `(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
 ;       ,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
 ;          ,@body)))))



 ; test do-primes
 (format t "~%=====~%primes:~%")
 (do-primes-4 (p 0 19) (format t "~a " p))
 (format t "~%=====~%")

 ; :DOC:
 ; rules of thumb:
 ; * Unless there's a particular reason to do otherwise, include any subforms in the expansion in positions that will be evaluated in the same order as the subforms appear in the macro call.
 ; * Unless there's a particular reason to do otherwise, make sure subforms are evaluated only once by creating a variable in the expansion to hold the value of evaluating the argument form and then using that variable anywhere else the value is needed in the expansion.
 ; * Use GENSYM at macro expansion time to create variable names used in the expansion.
 ; 
 
 ) ; test-do-primes-macro

;=================================================
; macros test
;
(defun test ()
 ; 1
 (test-when-macro)

 ; 2
 (test-do-primes-macro)
 )

;=================================================
; app entry point
;
(defun main ()

 (format t "custom macros sample~%=====~%")

 (test)

 (exit)
 )

;=================================================
; executes main
;
(main)
